<html>
    <title>Hi-C scaffolding viewer</title>
    <style>
        .background {
            fill: #eee;
        }

        line {
            stroke: #fff;
            stroke-opacity: .5;
            shape-rendering: crispEdges;
        }

        textarea {
            padding: 2px;
            width: 714px;
            height: 360px;
        }

        .loader {
            position: fixed;
            left: 0px;
            top: 0px;
            width: 100%;
            height: 100%;
            z-index: 9999;
            overflow: scroll;
            background: url() no-repeat center;
        }
    </style>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script>

// Borrowed from gist: https://gist.github.com/nvictus/88b3b5bfe587d32ac1ab519fd0009607
// Client-side parser for .npy files
// See the specification: http://docs.scipy.org/doc/numpy-dev/neps/npy-format.html
var NumpyLoader = (function () {
    function asciiDecode(buf) {
        return String.fromCharCode.apply(null, new Uint8Array(buf));
    }

    function readUint16LE(buffer) {
        var view = new DataView(buffer);
        var val = view.getUint8(0);
        val |= view.getUint8(1) << 8;
        return val;
    }

    function fromArrayBuffer(buf) {
      // Check the magic number
      var magic = asciiDecode(buf.slice(0,6));
      if (magic.slice(1,6) != 'NUMPY') {
          throw new Error('unknown file type');
      }

      var version = new Uint8Array(buf.slice(6,8)),
          headerLength = readUint16LE(buf.slice(8,10)),
          headerStr = asciiDecode(buf.slice(10, 10+headerLength));
          offsetBytes = 10 + headerLength;
          //rest = buf.slice(10+headerLength);  XXX -- This makes a copy!!! https://www.khronos.org/registry/typedarray/specs/latest/#5

      // Hacky conversion of dict literal string to JS Object
      eval("var info = " + headerStr.toLowerCase().replace('(','[').replace('),',']'));

      // Intepret the bytes according to the specified dtype
      var data;
      if (info.descr === "|u1") {
          data = new Uint8Array(buf, offsetBytes);
      } else if (info.descr === "|i1") {
          data = new Int8Array(buf, offsetBytes);
      } else if (info.descr === "<u2") {
          data = new Uint16Array(buf, offsetBytes);
      } else if (info.descr === "<i2") {
          data = new Int16Array(buf, offsetBytes);
      } else if (info.descr === "<u4") {
          data = new Uint32Array(buf, offsetBytes);
      } else if (info.descr === "<i4") {
          data = new Int32Array(buf, offsetBytes);
      } else if (info.descr === "<f4") {
          data = new Float32Array(buf, offsetBytes);
      } else if (info.descr === "<f8") {
          data = new Float64Array(buf, offsetBytes);
      } else {
          throw new Error('unknown numeric dtype')
      }

      return {
          shape: info.shape,
          fortran_order: info.fortran_order,
          data: data
      };
    }

    function open(file, callback) {
        var reader = new FileReader();
        reader.onload = function() {
            // the file contents have been read as an array buffer
            var buf = reader.result;
            var ndarray = fromArrayBuffer(buf);
            callback(ndarray);
        };
        reader.readAsArrayBuffer(file);
    }

    function ajax(url, callback) {
        var xhr = new XMLHttpRequest();
        xhr.onload = function(e) {
            var buf = xhr.response; // not responseText
            var ndarray = fromArrayBuffer(buf);
            callback(ndarray);
        };
        xhr.open("GET", url, true);
        xhr.responseType = "arraybuffer";
        xhr.send(null);
    }

    return {
        open: open,
        ajax: ajax
    };
})();

// openFile returns a fetch promise
function openFile(filename) {
    if (filename.endsWith(".json")) {
        return fetch(filename)
            .then(response => response.json());
    } else {
        return fetch(filename)
            .then(response => response.blob());
    }
};

// getMatrix converts a numpy array to Javascript 2D array
function getMatrix(array) {
    var n = array.shape[0],
        zero = d3.range(n).map(x => 0),
        matrix = zero.map(x => zero.slice());

    for (let i = 0; i < n; i ++) {
        for (let j = 0; j < n; j ++) {
            matrix[i][j] = array.data[i * n + j] + 1
        }
    }

    // return matrix.slice(0, 60).map(x => x.slice(0, 60))
    return matrix
}

// plotHeatMap does the plotting in d3
// Borrowed heavily from: https://bost.ocks.org/mike/shuffle/compare.html
function plotHeatMap(array) {
    // console.log(array);

    var margin = {top: 40, right: 40, bottom: 40, left: 40},
    width = 800,
    height = 800;

    var matrix = getMatrix(array);
    var n = matrix.length,
        m = 10000

    // console.log(matrix);
    var x = d3.scaleBand()
        .domain(d3.range(n))
        .range([0, width]);

    var z = d3.scaleSequential(d3.interpolatePuOr)
        .domain([0, 5])
        .clamp(true);

    var svg = d3.select("#canvas").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        // .style("margin-left", -margin.left + "px")
    .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    svg.append("rect")
        .attr("class", "background")
        .attr("width", width)
        .attr("height", height);

    var row = svg.selectAll(".row")
    .data(matrix)
    .enter().append("g")
        .attr("class", "row")
        .attr("transform", function(d, i) { return "translate(0," + x(i) + ")"; });

    row.selectAll(".cell")
    .data(function(d) { return d; })
    .enter().append("rect")
        .attr("class", "cell")
        .attr("x", function(d, i) { return x(i); })
        .attr("width", x.bandwidth())
        .attr("height", x.bandwidth());

    // row.append("line")
    //     .attr("x2", width);

    // X-axis label
    svg.append("text")
        .attr("x", width / 2)
        .attr("y", height + 20)
        .attr("fill", "lightslategray")
        .attr("text-anchor", "middle")
        .text("Bins (250kb per bin)")

    let axisFormat = (d, i) => (i % 20) == 0 ? i : "";

    row.append("text")
        .attr("x", -6)
        .attr("y", x.bandwidth() / 2)
        .attr("dy", "0.32em")
        .attr("text-anchor", "end")
        .text(axisFormat);

    var column = svg.selectAll(".column")
    .data(matrix)
    .enter().append("g")
        .attr("class", "column")
        .attr("transform", function(d, i) { return "translate(" + x(i) + ")rotate(-90)"; });

    // column.append("line")
    //     .attr("x1", -width);

    column.append("text")
        .attr("x", 6)
        .attr("y", x.bandwidth() / 2)
        .attr("dy", "0.32em")
        .attr("text-anchor", "start")
        .text(axisFormat);

    row.selectAll(".cell")
      .data(function(d, i) { return matrix[i]; })
      .style("fill", function(d) { return z(Math.log(d + 1)); });
};

// main entry point
async function main() {
    let genome = await openFile("genome.json");
    let blob = await openFile("data.npy");
    NumpyLoader.open(blob, array => plotHeatMap(array))
};

    </script>
    <head>
    </head>
    <body onload="main()">
        <div id="canvas" class="loader" align="center"></div>
    </body>
</html>